using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Web; 
using Rest;

namespace Rest.Http
{
    /// <summary>
    /// Encoding: "multipart/form-data"
    /// </summary>
    /// <remarks>
    /// <p>
    /// How To Build A multipart/form-data POST request
    /// See: http://chxo.com/be2/20050724_93bf.html
    /// 
    /// Form-based file uploads
    /// See: http://www.faqs.org/rfcs/rfc1867.html
    /// </p>
    /// </remarks> 
    public class MultipartFormData : HttpContentType
    {
        #region Properties

        /// <summary>
        /// Note: This must not appear in any of the data
        /// </summary>
        private String boundary = "PostRequestTransport-" +
           DateTime.Now.ToString("yyyyMMddhHHmmss");

        private String contentType = null;

        private System.Text.Encoding encoding = Encoding.UTF8;

        /// <summary>
        /// The boundary for multipart messages
        /// </summary>
        public String Boundary
        {
            get { return boundary; }
            set { boundary = value; }
        }

        /// <summary>
        /// The content type for this instance (multipart/form-data).
        /// Correspnds to the Content-type header
        /// </summary>
        /// <remarks>
        /// Should be of the format: multipart/form-data, boundary=AaB03x</remarks>
        public sealed override String ContentType
        {
            get 
            {
                return contentType ??
                    (contentType = String.Format("multipart/form-data, boundary={0}", Boundary)); 
            }
            set
            {
                contentType = value; 
            }
        }

        #endregion

        /// <summary>
        /// Appends the supplied RestDataItem to the supplied stream
        /// </summary>
        /// <returns></returns>
        private void AppendDataItem(
            Stream stream,
            RestDataItem dataItem, 
            String boundary, 
            Encoding encoding)
        {
            StringBuilder dataItemBuilder = new StringBuilder();
            Byte[] newlineBytes = encoding.GetBytes("\r\n");

            //
            // Append reference to the variable
            //
            // TODO: Is the following required
            // AppendNameValuePair(dataItemBuilder, dataItem.Name, String.Empty, boundary);

            dataItemBuilder.Append("--" + boundary + "\r\n");

            dataItemBuilder.AppendFormat(
                "Content-Disposition: form-data; {0}\r\n",
                GetAttributeString(dataItem));

            dataItemBuilder.AppendFormat(
                "Content-Type: {0}\r\n",
                dataItem.ContentType);

            dataItemBuilder.Append("\r\n");

            stream.Write(encoding.GetBytes(dataItemBuilder.ToString()), 0,
                encoding.GetByteCount(dataItemBuilder.ToString()));

            if (dataItem.Length > 0)
            {
                stream.Write(dataItem.GetBytes(), 0, dataItem.Length);
            }

            // Close with new line
            stream.Write(newlineBytes, 0, newlineBytes.Length);
        }

        /// <summary>
        /// Appends a name/value pair
        /// </summary>
        /// <param Name="buffer"></param>
        /// <param Name="parameter"></param>
        /// <param Name="boundary">The boundary string</param>
        private void AppendNameValuePair(StringBuilder buffer,
            String name, String value, String boundary)
        {
            /*
            // 2008-04-02: SpokenText. It seems we may not be expected to UrlEncode values here.
            //buffer.Append("--" + boundary + "\r\n");
            //buffer.Append("Content-Disposition: form-data; name=\"" +
            //    HttpUtility.UrlEncode(name) + "\"");
            //buffer.Append("\r\n\r\n");
            //buffer.Append(HttpUtility.UrlEncode(value) + "\r\n");
            */

            buffer.Append("--" + boundary + "\r\n");
            buffer.AppendFormat("Content-Disposition: form-data; name=\"{0}\"", name);
            buffer.Append("\r\n\r\n");
            buffer.AppendFormat("{0}\r\n", value);
        }

        /// <summary>
        /// Appends a string to the supplied buffer
        /// </summary>
        /// <param Name="buffer"></param>
        /// <param Name="parameter"></param>
        /// <param Name="boundary">The boundary string</param>
        private void AppendRestParameter(StringBuilder buffer,
            RestParameter parameter, String boundary)
        {
            AppendNameValuePair(buffer, parameter.Name, parameter.Value, boundary);
        }

        /// <summary>
        /// Creates a single string of name/value pairs from the
        /// Attributes collection of the supplied RestDataItem
        /// </summary>
        /// <remarks>
        /// pairs are delimited by semi-colons.
        /// Example: name="value"; name-1="value-1"; name-2="value-2"
        /// </remarks> 
        /// <param name="dataItem">The RestDataItem whose attributes are to be 
        /// converted to single string</param>
        /// <returns>Either a string of name/value pairs delimited by semicolons, 
        /// or null if dataItem contains no items in its Attributes collection</returns>
        private String GetAttributeString(RestDataItem dataItem)
        {
            if (dataItem.Attributes != null)
            {
                StringBuilder attributeBuilder = new StringBuilder();

                for (Int32 i = 0; i < dataItem.Attributes.Count; i++)
                {
                    attributeBuilder.AppendFormat("{0}=\"{1}\"{2}",
                        dataItem.Attributes.Keys[i],
                        dataItem.Attributes[dataItem.Attributes.Keys[i]],
                        i < dataItem.Attributes.Count - 1 ? "; " : String.Empty);
                }

                return attributeBuilder.ToString();
            }

            return null;
        }

        /// <summary>
        /// Write request body directly to the supplied stream
        /// </summary>
        /// <param name="stream"></param>
        /// <param name="parameters"></param>
        /// <param name="dataItems"></param>
        /// <param name="characterEncoding"></param>
        public sealed override void WriteRequestBody(
            Stream stream,
            RestParameterCollection parameters,
            RestDataItemCollection dataItems,
            Encoding characterEncoding)
        {
            StringBuilder postBodyBuilder = new StringBuilder();
            Byte[] footerBytes = characterEncoding.GetBytes("\r\n--" + Boundary + "--\r\n");

            //
            // Add parameters
            //
            if (parameters != null && parameters.Count > 0)
            {
                foreach (RestParameter parameter in parameters)
                {
                    AppendRestParameter(postBodyBuilder, parameter, Boundary);
                }
                Byte[] parameterBytes = characterEncoding.GetBytes(postBodyBuilder.ToString());
                stream.Write(parameterBytes, 0, parameterBytes.Length);
            }

            //
            // Add data items
            //
            if (dataItems != null && dataItems.Count > 0)
            {
                foreach (RestDataItem dataItem in dataItems)
                {
                    AppendDataItem(stream, dataItem, Boundary,
                        characterEncoding);
                }
            }

            //
            // Add the footer
            // TODO: BJB, 16-12-2006 : Not sure if this is required. 
            // It seems to add 2 bytes to the files at the request end.
            stream.Write(footerBytes, 0, footerBytes.Length);
        }
    }
}